package videolib;

import Entities.Category;
import Entities.Medium;
import Tools.DatabaseException;
import Managers.*;
import Tools.ODSWorker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * Created by Václav Bukovský on 10. 6. 2014.
 * author: Václav Bukovský
 * version: 2014-06-10
 * description: Class which provides connection between back-end and front-end.
 *              This class uses methods for working with database and handles requests from jsp pages
 *              ,transfer data to jsp pages and perform redirecting.
 */
// Zajištění, že všechny tyhle akce bude servlet obsluhovat!
@WebServlet(name = "LibraryServlet", urlPatterns = {
        MyLibraryServlet.ACTION_MAIN_PAGE,
        MyLibraryServlet.ACTION_CAT_LIB,
        MyLibraryServlet.ACTION_MED_LIB,
        MyLibraryServlet.ACTION_C_C,
        MyLibraryServlet.ACTION_C_M,
        MyLibraryServlet.ACTION_SEARCH,
        MyLibraryServlet.ACTION_MED_DEL,
        MyLibraryServlet.ACTION_CAT_DEL,
        MyLibraryServlet.ACTION_IMP,
        MyLibraryServlet.ACTION_EXP,
        MyLibraryServlet.ACTION_MED_UPD,
        MyLibraryServlet.ACTION_CAT_UPD
})
public class MyLibraryServlet extends HttpServlet{

    public static final Logger logger = LoggerFactory.getLogger(MyLibraryServlet.class);

    // Akce
    static final String ACTION_EXP = "/export";
    static final String ACTION_IMP = "/import";
    static final String ACTION_CAT_DEL = "/deleteCategory";
    static final String ACTION_MED_DEL = "/deleteMedium";
    static final String ACTION_SEARCH = "/search";
    static final String ACTION_MED_LIB = "/mediums";
    static final String ACTION_CAT_LIB = "/categories";
    static final String ACTION_MAIN_PAGE = "/index";
    static final String ACTION_C_C = "/createCategory";
    static final String ACTION_C_M = "/createMedium";
    static final String ACTION_MED_UPD = "/updateMedium";
    static final String ACTION_CAT_UPD = "/updateCategory";

    // Jsp stranky
    static final String JSP_INDEX = "/index.jsp";
    static final String JSP_MED = "/mediums.jsp";
    static final String JSP_CAT = "/categories.jsp";

    // Pomocne promene
    private String result = null;
    private String deletedMed = null;
    private String deletedCat = null;
    private String searchedCategory = null;
    private String searchedMedium = null;

    /**
     * Method for gaining access to medium manager.
     * @return medium manager
     */
    private MediumManager getMediumManager() {
        return (MediumManager) getServletContext().getAttribute("mediumManager");
    }

    /**
     * Method for gaining access to category manager.
     * @return category manager
     */
    private CategoryManager getCategoryManager(){
        return (CategoryManager) getServletContext().getAttribute("categoryManager");
    }

    /**
     * Method for gaining access to library manager.
     * @return library manager
     */
    private LibraryManager getLibraryManager(){
        return (LibraryManager) getServletContext().getAttribute("libraryManager");
    }

    /**
     * Method for servicing main page. The method sends to jsp page the contents of the database.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void mainPage(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
        try{
            request.setAttribute("mainList", getLibraryManager().getDatabaseContentAsXMLDocument(true));

            request.getRequestDispatcher(JSP_INDEX).forward(request, response);
        }catch(DatabaseException ex) {
            logger.debug("Something go wrong displaying main page.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method for servicing "categories" page. Method servicing delete and categories view.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void categories(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        if (deletedCat != null) {
            request.setAttribute("deletedCat", deletedCat);
            deletedCat = null;
        }

        try {
            request.setAttribute("listCategories", getLibraryManager().getDatabaseContentAsXMLDocument(false));

            request.getRequestDispatcher(JSP_CAT).forward(request, response);
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong displaying categories page.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing creating category. Method checks if the category isn't in the database.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void newCategory(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        try {
            String name = request.getParameter("name");


            Category category = new Category();
            category.setName(name);
            if (getCategoryManager().getCategoryByName(name) != null) {
                request.setAttribute("createError", "Category \"" + name + "\" already exist!");
                request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
            }
            else {
                getCategoryManager().createCategory(category);
                request.setAttribute("createMessage", "Category \"" + name + "\" successfully created!");
                request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong creating new category.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method for servicing "mediums" page. Method servicing delete, search and categories view.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void mediums(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        if (result != null) {
            request.setAttribute("resultSearch", result);
            result = null;
        }

        if (deletedMed != null) {
            request.setAttribute("deletedMed", deletedMed);
            deletedMed = null;
        }

        try {
            request.setAttribute("listMediums", getLibraryManager().getAllMediumsAsXMLDocument());
            request.setAttribute("categories", getCategoryManager().listAllCategories());

            request.getRequestDispatcher(JSP_MED).forward(request, response);

        }catch (DatabaseException ex) {
            logger.debug("Something go wrong displaying mediums page.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing creating medium. Method checks if the medium isn't in the database.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void newMedium(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        try {
            String label = request.getParameter("label");
            String type = request.getParameter("typeDropdown");
            String categoryName = request.getParameter("categoriesDropdown");
            String movies = request.getParameter("movies");

            if(getMediumManager().getMediumByLabel(label) != null){
                request.setAttribute("createError", "Medium \"" + label + "\" already exist!");
                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            } else {
                Medium medium = new Medium();
                medium.setLabel(label);
                medium.setType(type);
                medium.setMovies(parseMovies(movies));
                getMediumManager().createMedium(medium);

                Category chosenCategory = getCategoryManager().getCategoryByName(categoryName);

                getLibraryManager().insertMediumIntoCategory(medium, chosenCategory);

                request.setAttribute("createMessage","Medium \"" + label + "\" successfully created!");
                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong creating new medium.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing search medium. When the media is not found, the method displays a warning.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void search(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String searchedName = request.getParameter("search");
        Medium medium;
        try {
            medium = getMediumManager().getMediumByLabel(searchedName);
            if(medium == null) {
                request.setAttribute("searchError","Medium \""+ searchedName +"\" not found!");
                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            } else {
                medium = getMediumManager().getMediumByLabel(searchedName);
                result = getLibraryManager().getMediumAsString(medium.getId());

                request.setAttribute("resultSearch", result);

                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong searching for medium.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing delete medium. When the deleting media is not found, the method displays a warning.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void deleteMedium(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String label = request.getParameter("delete");
        try {
            Medium medium = getMediumManager().getMediumByLabel(label);
            if(medium == null){
                request.setAttribute("deleteError", "Medium \"" + label + "\" doesn't exist!");
                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            } else {
                Category category = getLibraryManager().getCategoryByMedium(medium);
                getLibraryManager().removeMediumFromCategory(medium, category);
                getMediumManager().deleteMedium(medium);
                deletedMed = medium.getLabel();
                request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong deleting medium.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing delete category. When the deleting category is not found, the method displays a warning.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void deleteCategory(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("delete");
        try {
            Category category = getCategoryManager().getCategoryByName(name);
            if(category == null){
                request.setAttribute("deleteError","Category \"" + name + "\"doesn't exist!");
                request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
            } else if (category.getMediums().size() != 0) {
                request.setAttribute("deleteError","Category have to be empty!");
                request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
            } else {
                getCategoryManager().deleteCategory(category);
                deletedCat = category.getName();
                request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong deleting category.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing update medium.
     * The method first finds the medium and then update it. When you update, method checks the correctness of data.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void updateMedium(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String label = request.getParameter("update");
        try {
            if (searchedMedium == null) {
                Medium medium = getMediumManager().getMediumByLabel(label);
                if (medium == null) {
                    request.setAttribute("updateError", "Medium \"" + label + "\" not found!");
                    request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
                } else {
                    searchedMedium = label;
                    request.setAttribute("uLabel",medium.getLabel());
                    request.setAttribute("ucategory", getLibraryManager().getCategoryByMedium(medium).getName());
                    request.setAttribute("searchedMedium", searchedMedium);
                    request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
                }
            } else {
                if(request.getParameter("Cancel") != null){
                    searchedMedium = null;
                    request.setAttribute("searchedMedium", null);
                    request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
                } else {
                    String labelName = request.getParameter("uLabel");
                    String type = request.getParameter("uTypeDropdown");
                    String category = request.getParameter("uCategoriesDropdown");

                    if (getMediumManager().getMediumByLabel(labelName) != null) {
                        request.setAttribute("uError", "Medium \"" + labelName + "\" already exist!");
                        request.setAttribute("uLabel", labelName);
                        request.setAttribute("ucategory", category);
                        request.setAttribute("searchedMedium", searchedMedium);
                        request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
                    } else {
                        Medium med = getMediumManager().getMediumByLabel(searchedMedium);

                        med.setLabel(labelName);
                        med.setType(type);

                        Category newCategory = getCategoryManager().getCategoryByName(category);
                        Category oldCategory = getLibraryManager().getCategoryByMedium(med);
                        getLibraryManager().removeMediumFromCategory(med, oldCategory);
                        getLibraryManager().insertMediumIntoCategory(med, newCategory);

                        getMediumManager().updateMedium(med);
                        searchedMedium = null;
                        request.setAttribute("searchedMedium", null);
                        request.getRequestDispatcher(ACTION_MED_LIB).forward(request, response);
                    }
                }
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong updating medium.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method servicing update category.
     * The method first finds the category and then it update. When you update, method checks the correctness of data.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void updateCategory(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String label = request.getParameter("update");
        try {
            if (searchedCategory == null) {
                Category category = getCategoryManager().getCategoryByName(label);
                if (category == null) {
                    request.setAttribute("updateError", "Category \"" + label + "\" not found!");
                    request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
                } else {
                    searchedCategory = label;
                    request.setAttribute("searchedCategory", searchedCategory);
                    request.setAttribute("uName", label);
                    request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
                }
            }else {
                if(request.getParameter("Cancel") != null){
                    searchedCategory = null;
                    request.setAttribute("searchedCategory", null);
                    request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
                } else {
                    String name = request.getParameter("uName");
                    if (getCategoryManager().getCategoryByName(name) != null) {
                        request.setAttribute("uError", "Category \"" + name + "\" already exist!");
                        request.setAttribute("uName", name);
                        request.setAttribute("searchedCategory", searchedCategory);
                        request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
                    } else {
                        Category cat = getCategoryManager().getCategoryByName(searchedCategory);
                        getCategoryManager().renameCategory(cat, name);
                        searchedCategory = null;
                        request.setAttribute("searchedCategory", null);
                        request.getRequestDispatcher(ACTION_CAT_LIB).forward(request, response);
                    }
                }
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong updating category.");
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
        }
    }

    /**
     * Method securing import from .ods file. The method checks correctness specified file path.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void importODS(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        ODSWorker parser = new ODSWorker(getCategoryManager(), getMediumManager(), getLibraryManager());
        String path = request.getParameter("pathImport");
        if (parser.isODFValid(path)) {
            System.out.println("VALID");
            parser.importFromODF(path);
        } else {
            request.setAttribute("imExError", "Invalid path or file.");
        }
        request.getRequestDispatcher(ACTION_MAIN_PAGE).forward(request, response);
    }

    /**
     * Method securing export from .ods file. The method checks correctness specified file path.
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    public void exportODS(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ODSWorker parser = new ODSWorker(getCategoryManager(), getMediumManager(), getLibraryManager());
        String OS = System.getProperty("os.name").toLowerCase();
        String path = request.getParameter("pathExport");
        try {
            String folderPath;
            if(OS.indexOf("win") >= 0) {
                folderPath = path.substring(0, path.lastIndexOf("\\"));
            }
            else {
                folderPath = path.substring(0, path.lastIndexOf("/"));
            }
            if ((new File(folderPath).exists()) && (path.endsWith(".ods"))) {
                parser.exportToODF(path, getCategoryManager().listAllCategories());
            } else {
                request.setAttribute("imExError", "Invalid path or file");
            }
        } catch (DatabaseException ex) {
            logger.debug("Something go wrong exporting ODS.");
        }
        request.getRequestDispatcher(ACTION_MAIN_PAGE).forward(request, response);
    }

    /**
     * Auxiliary method for validating and obtaining entered films in creating media.
     * @param movies String from page to parse
     * @return processed movies
     */
    private List<String> parseMovies(String movies) {
        if(movies == null) {
            throw new NullPointerException("Argument is null");
        }
        if(movies.equals("")){
            return new ArrayList<>();
        }
        String[] parts = movies.split("(,)|(\n)");
        return Arrays.asList(parts);
    }

    /**
     * Auxiliary method ensuring serve of events sent from the JSP files and call methods serving these events.
     *
     * @param request HTTP request
     * @param response HTTP response
     * @throws IOException when error occurs in communication between servlet and pages
     * @throws ServletException when error occurs in communication between servlet and pages
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        if (request.getServletPath().equals(ACTION_MAIN_PAGE)) {
            mainPage(request,response);
        } else if (request.getServletPath().equals(ACTION_CAT_LIB)) {
            categories(request, response);
        } else if (request.getServletPath().equals(ACTION_C_C)) {
            newCategory(request, response);
        } else if (request.getServletPath().equals(ACTION_MED_LIB)) {
            mediums(request, response);
        } else if (request.getServletPath().equals(ACTION_C_M)){
            newMedium(request, response);
        } else if (request.getServletPath().equals(ACTION_SEARCH)){
            search(request, response);
        } else if (request.getServletPath().equals(ACTION_MED_DEL)){
            deleteMedium(request, response);
        } else if (request.getServletPath().equals(ACTION_CAT_DEL)) {
            deleteCategory(request, response);
        } else if (request.getServletPath().equals(ACTION_IMP)) {
            importODS(request, response);
        } else if (request.getServletPath().equals(ACTION_EXP)){
            exportODS(request, response);
        } else if (request.getServletPath().equals(ACTION_MED_UPD)){
            updateMedium(request, response);
        } else if (request.getServletPath().equals(ACTION_CAT_UPD)){
            updateCategory(request, response);
        } else {
            throw new RuntimeException("Unknown operation " + request.getServletPath());
        }

    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        String servletInfo = "Servlet je vytvoren Vaclavem. Slouzi k obsluze JSP stranek a redirectingu.";
        return servletInfo;
    }




}